/**
 * @file   Logger.hpp
 * @author Yuan Liu <liuyuan@net.pku.edu.cn>
 * @date   Tue Aug 28 17:59:16 2007
 * 
 * @brief  A light-weighted logger
 *
 * Basic capability:
 *    1. Thread-safty [removed]
 *    2. Five levels : DEBUG, INFO, WARN, ERROR, FATAL
 *    3. Split large file stream
 * 
 * @version 0.1
 *
 * @todo optional thread-safty 
 */

#ifndef __LOGGER_HPP
#define __LOGGER_HPP

#include <vector>
#include <string>
#include <ostream>
#include <fstream>
#include <sstream>

#include <time.h>

#define LOG_DEBUG(logger) logger->Print(DEBUG_L, __FILE__, __LINE__)
#define LOG_INFO(logger)  logger->Print(INFO_L, __FILE__, __LINE__)
#define LOG_WARN(logger)  logger->Print(WARN_L, __FILE__, __LINE__)
#define LOG_ERROR(logger) logger->Print(ERROR_L, __FILE__, __LINE__)
#define LOG_FATAL(logger) logger->Print(FATAL_L, __FILE__, __LINE__)

#define LOG_TIME(logger) logger->Time()

const int DEFAULT_LOG_FILE_SIZE = 512 * 1024 * 1024; /**< 500M per file */

enum LOG_LEVEL
{
    DEBUG_L,             /**< For debug, including file-line-function */
    INFO_L,              /**< For information */
    WARN_L,              /**< For some faild tasks */
    ERROR_L,             /**< For error must be solve */
    FATAL_L              /**< For fatal error, the program will be interupted */
};

class Logger
{
public:
    Logger()
        : mLogStream(NULL), mLogLevel(DEBUG_L), mFileIdx(0), mIsFile(false)
    {
        SetColor(false);
        mNulStream = new std::ofstream("/dev/null");
        // colorful log message
        mMessageColor.push_back("\033[01;32m[ DEBUG ]\033[00m -- ");
        mMessageColor.push_back("\033[01;34m[ INFO  ]\033[00m -- ");
        mMessageColor.push_back("\033[01;35m[ WARN  ]\033[00m -- ");
        mMessageColor.push_back("\033[01;33m[ ERROR ]\033[00m -- ");
        mMessageColor.push_back("\033[01;31m[ FATAL ]\033[00m -- ");
        // normal log message
        mMessageNon.push_back("[ DEBUG ] -- ");
        mMessageNon.push_back("[ INFO  ] -- ");
        mMessageNon.push_back("[ WARN  ] -- ");
        mMessageNon.push_back("[ ERROR ] -- ");
        mMessageNon.push_back("[ FATAL ] -- ");
    }

    ~Logger()
    {
        if (mIsFile) {
            if (mLogStream != NULL) {
                delete mLogStream;
            }
        }
        delete mNulStream;
    }
    
    /// use stream
    bool Init(std::ostream* logStream);
    /// use file
    bool Init(const char* pFileName, int size = DEFAULT_LOG_FILE_SIZE);

    /// shield the low level log message
    void SetLogLevel(LOG_LEVEL logLevel)
    {
        mLogLevel = logLevel;
    }
    
    void SetColor(bool color)
    {
        if (color)
            mMessage = &mMessageColor;
        else
            mMessage = &mMessageNon;
    }

    /// output the log message
    std::ostream& Print(LOG_LEVEL, const char*, int);
    
    std::ostream& Time();
    
private:
    bool FileOutRange();

private:
    /// basic operation
    std::ostream* mLogStream;
    std::ostream* mNulStream;
    LOG_LEVEL     mLogLevel;

    /// for color message
    std::vector<std::string>* mMessage;
    
    /// for log file 
    std::string mFileName;
    int         mSizeLimit;
    int         mFileIdx;
    bool        mIsFile;

    std::vector<std::string> mMessageColor;
    std::vector<std::string> mMessageNon;
};

inline bool
Logger::Init(std::ostream* logStream)
{
    if (mIsFile) {              // re-init this logger
        mIsFile = false;
        delete mLogStream;
    }
    mLogStream = logStream;
    return true;
}

inline bool
Logger::Init(const char* pFileName, int size)
{
    if (mIsFile) {
        delete mLogStream;
    }
    
    mIsFile = true;
    mFileName = pFileName;
    
    if (size > DEFAULT_LOG_FILE_SIZE * 2) {
        // discourage file larger than 1G
        size = DEFAULT_LOG_FILE_SIZE;
    }
    mSizeLimit = size;
    
    mLogStream = new std::ofstream(pFileName);
    if (mLogStream == NULL) {
        return false;
    }
    return true;
}

inline bool
Logger::FileOutRange()
{
    bool is_file_out_range = false;
    int  size_current      = mLogStream->tellp();

    if (size_current > mSizeLimit) {
        is_file_out_range = true;
        delete mLogStream;
        std::ostringstream new_file_name;
        new_file_name << mFileName << "." << mFileIdx;
        mLogStream = new std::ofstream(new_file_name.str().c_str());

        ++mFileIdx;
    }
    return is_file_out_range;
}

inline std::ostream&
Logger::Print(LOG_LEVEL logLevel, const char* file, int line)
{
    if (mLogLevel > logLevel) {
        return *mNulStream;
    }

    if (mIsFile) {
        FileOutRange();
    }
    
    std::string file_only = file;
    std::string::size_type pos = file_only.rfind("/");
    if (pos != std::string::npos) 
        file_only = file_only.substr(pos+1);

    *mLogStream << (*mMessage)[logLevel] << "In " << file_only << " at line " << line << " : ";
    
    return *mLogStream;
}

inline std::ostream&
Logger::Time()
{
    // get current time
    time_t cur_time;
    cur_time = time(NULL);
    // convert time to local time
    struct tm* loctime;
    loctime = localtime(&cur_time);

    std::string asci_time = asctime(loctime);
    
    asci_time = asci_time.substr(0, asci_time.length()-1);
    
    *mLogStream << asci_time;

    if (mIsFile) {
        FileOutRange();
    }

    return *mLogStream;
}

#endif
